#include "StdAfx.h"
#include "ImpersonationSession.h"

using namespace Adaptive::Foundation;
using namespace Adaptive::Win32;

/*--------------------------------------------------------------------------------
 * Constructor / Dispose / Destructor Methods
 *------------------------------------------------------------------------------*/
#pragma region Constructor / Dispose / Destructor Methods
ImpersonationSession::ImpersonationSession()
{
	_originalLoginToken = IntPtr::Zero;
	_duplicatedToken = IntPtr::Zero;
	_newIdentity = nullptr;
	_impersonationContext = nullptr;
}
void ImpersonationSession::!ImpersonationSession()
{
    if (!Disposed)
    {
        CloseSession();
    }

	_originalLoginToken = IntPtr::Zero;
	_duplicatedToken = IntPtr::Zero;
    _newIdentity = nullptr;
    _impersonationContext = nullptr;
}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Public Properties
 *------------------------------------------------------------------------------*/
#pragma region Public Properties
IntPtr ImpersonationSession::LoginToken::get()
{
	return _originalLoginToken;
}
IntPtr ImpersonationSession::ImpersonationToken::get()
{
	return _duplicatedToken;
}
WindowsIdentity^ ImpersonationSession::ImpersonationIdentity::get()
{
	return _newIdentity;
}
WindowsImpersonationContext^ ImpersonationSession::ImpersonationContext::get()
{
	return _impersonationContext;
}
String^ ImpersonationSession::CurrentUser::get()
{
	return WindowsIdentity::GetCurrent()->Name;
}
#pragma endregion


/*--------------------------------------------------------------------------------
 * Public Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Public Methods / Functions
void ImpersonationSession::CloseSession()
{
    if ((_newIdentity != nullptr) && (_impersonationContext != nullptr))
    {
            if (_impersonationContext != nullptr)
            {
                _impersonationContext->Undo();
                delete(_impersonationContext);
            }
            if (_newIdentity != nullptr)
            {
                delete(_newIdentity);
            }
			if (_duplicatedToken != IntPtr::Zero)
                CloseOpenHandle(_duplicatedToken);

            if (_originalLoginToken != IntPtr::Zero)
                CloseOpenHandle(_originalLoginToken);

            _impersonationContext = nullptr;
            _newIdentity = nullptr;
            _duplicatedToken = IntPtr::Zero;
            _originalLoginToken = IntPtr::Zero;
			GC::Collect();
        
			OnImpersionationEnded(EventArgs::Empty);
	}
}
bool ImpersonationSession::InitializeSession(String^ domain, String^ userName, String^ password)
{
	bool returnValue = false;                   //Return value.

	//Attempt the login.
	_originalLoginToken = PerformUserLogin(domain, userName, password);
	if (_originalLoginToken != IntPtr::Zero)
	{
		OnUserLoginSuccess(EventArgs::Empty);
		//Create the impersonation token.
		_duplicatedToken = PerformTokenDuplication();
		if (_duplicatedToken != IntPtr::Zero)
		{
			//Assign the identity object.
			if (CreateIdentity())
			{
				//Invoke the impersonation.
				returnValue = CreateContext();
				if(!returnValue)
				{
					//Clean up on failure.
					OnImpersonationContextFailure(EventArgs::Empty);
					delete(_newIdentity);
					_newIdentity = nullptr;
					CloseOpenHandle(_duplicatedToken);
					_duplicatedToken = IntPtr::Zero;
					CloseOpenHandle(_originalLoginToken);
					_originalLoginToken = IntPtr::Zero;
				}
				else
					OnImpersionationSuccess(EventArgs::Empty);
			}
			else
			{
				//Clean up on failure.
				OnImpersonationContextFailure(EventArgs::Empty);
				CloseOpenHandle(_duplicatedToken);
				_duplicatedToken = IntPtr::Zero;
				CloseOpenHandle(_originalLoginToken);
				_originalLoginToken = IntPtr::Zero;
			}
		}
		else
		{
			//Clean up on failure.
			OnTokenDuplicationFailure(EventArgs::Empty);
			CloseOpenHandle(_originalLoginToken);
			_originalLoginToken = IntPtr::Zero;
		}
	}
	else
	{
		//Clean up on failure.
		OnUserLoginFailure(EventArgs::Empty);
		_originalLoginToken = IntPtr::Zero;
	}
	return returnValue;

}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Protected Event Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Protected Event Methods / Functions
/// <summary>
/// Raises the <see cref="UserLoginSuccess"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnUserLoginSuccess(EventArgs^ e)
{	
	UserLoginSuccess(this, e);
}
/// <summary>
/// Raises the <see cref="UserLoginFailure"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnUserLoginFailure(EventArgs^ e)
{
	UserLoginFailure(this, e);
}
/// <summary>
/// Raises the <see cref="TokenDuplicationFailure"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnTokenDuplicationFailure(EventArgs^ e)
{
	TokenDuplicationFailure(this, e);
}
/// <summary>
/// Raises the <see cref="ImpersonationContextFailure"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnImpersonationContextFailure(EventArgs^ e)
{
	ImpersonationContextFailure(this, e);
}
/// <summary>
/// Raises the <see cref="ImpersionationSuccess"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnImpersionationSuccess(EventArgs^ e)
{
	ImpersionationSuccess(this, e);
}
/// <summary>
/// Raises the <see cref="ImpersionationEnded"/> event.
/// </summary>
/// <param name="e">The <see cref="System::EventArgs"/> instance containing the event data.</param>
void ImpersonationSession::OnImpersionationEnded(EventArgs^ e)
{
	ImpersionationEnded(this, e);
}
#pragma endregion
	
/*--------------------------------------------------------------------------------
 * Private Methods / Functions
 *------------------------------------------------------------------------------*/
#pragma region Private Methods / Functions
IntPtr ImpersonationSession::PerformUserLogin(String^ domain, String^ userName, String^ password)
{
	IntPtr returnValue = IntPtr::Zero;

    returnValue = SafeLogonUser(userName, domain, password, 2, LOGON32_PROVIDER_DEFAULT);

    //Try under another type if failed...
    if (returnValue == IntPtr::Zero)
        returnValue = SafeLogonUser(userName, domain, password, 1, LOGON32_PROVIDER_DEFAULT);

    //Try under another type if failed...
    if (returnValue == IntPtr::Zero)
        returnValue = SafeLogonUser(userName, domain, password, 3, LOGON32_PROVIDER_DEFAULT);

    //Try under another provider if failed...
    if (returnValue == IntPtr::Zero)
        returnValue = SafeLogonUser(userName, domain, password, 2, LOGON32_PROVIDER_WINNT50 );

    return returnValue;

}
IntPtr ImpersonationSession::PerformUserLogin(String^ userName, String^ password)
{
	return PerformUserLogin(String::Empty, userName, password);
}
IntPtr ImpersonationSession::PerformTokenDuplication()
{
	IntPtr returnValue = IntPtr::Zero;

    returnValue = DuplicateUserToken(_originalLoginToken, 2);
    if (returnValue == IntPtr::Zero)
        returnValue = DuplicateUserToken(_originalLoginToken, 1);
    if (returnValue == IntPtr::Zero)
        returnValue = DuplicateUserToken(_originalLoginToken, 0);
    if (returnValue == IntPtr::Zero)
        returnValue = DuplicateUserToken(_originalLoginToken, 3);
    if (returnValue == IntPtr::Zero)
        returnValue = DuplicateUserToken(_originalLoginToken, 4);

    return returnValue;

}
bool ImpersonationSession::CreateIdentity()
{
    bool returnValue = false;

    if (_newIdentity == nullptr)
    {
        try
        {
            _newIdentity = gcnew WindowsIdentity(_duplicatedToken);
            returnValue = true;
        }
        catch(Exception^)
        {
            //OnExceptionCaught(new ExceptionEventArgs(ex));
            _newIdentity = nullptr;
        }
    }
    return returnValue;

}
bool ImpersonationSession::CreateContext()
{
	 bool returnValue = false;

    if ((_newIdentity != nullptr) && (_impersonationContext == nullptr))
    {
        try
        {
            _impersonationContext = _newIdentity->Impersonate();
            returnValue = true;
        }
        catch(Exception^)
        {
            //OnExceptionCaught(new ExceptionEventArgs(ex));
            _impersonationContext = nullptr;
        }
    }
    return returnValue;
}
#pragma endregion

/*--------------------------------------------------------------------------------
 * Private "Safe" API Function Wrappers
 *------------------------------------------------------------------------------*/
#pragma region Private Safe API Function Wrappers
IntPtr ImpersonationSession::SafeLogonUser(String^ userId, String^ domain, String^ password,
									int logonType, int logonProvider)
{
    BOOL result = false;                        //Last result.
	HANDLE* newHandle = new HANDLE();			//Result of user call.
	CString userIdString;
	CString domainString;
	CString passwordString;
	IntPtr returnValue;

	userIdString = userId;
	domainString = domain;
	passwordString = password;

    try
    {

        result = (BOOL)LogonUser(userIdString, domainString, passwordString, logonType, logonProvider, newHandle);
        if (!newHandle)
        {
			returnValue = IntPtr::Zero;
			//this->On(gcnew ExceptionEventArgs(gcnew Win32Exception(GetLastError())));
        }
		else
			returnValue = *(gcnew IntPtr(newHandle));

    }
    catch (Exception^)
    {
		returnValue = IntPtr::Zero;
        //OnExceptionCaught(gcnew ExceptionEventArgs(ex));
    }

    return returnValue;
}
bool ImpersonationSession::CloseOpenHandle(IntPtr handleValue)
{
    BOOL returnValue = false;           //Return value.

	if (handleValue != IntPtr::Zero)
    {
        try
        {
            returnValue = CloseHandle((HANDLE)handleValue.ToInt32());
        }
        catch (Exception^)
        {
        }
    }
    return (returnValue == 1);

}
IntPtr ImpersonationSession::DuplicateUserToken(IntPtr originalToken, int impersonationLevel)
{
    IntPtr returnValue;								//Return value.
    BOOL result = false;                            //Result of operation.
	HANDLE original;
	HANDLE* newToken;

    try
    {
		original = (HANDLE)originalToken.ToInt32();
		newToken = new HANDLE();

		result = DuplicateToken(original, (SECURITY_IMPERSONATION_LEVEL)impersonationLevel, newToken);
        if (!result)
        {
			returnValue = IntPtr::Zero;
        }
		else
		{
			returnValue = *(gcnew IntPtr(newToken));
		}
    }
    catch (Exception^)
    {
    }

    return returnValue;
}
#pragma endregion
